package com.lanp.springsd.config.database;

import com.lanp.springsd.config.sharding.algorithm.StandardModuloShardingTableAlgorithm;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.keygen.KeyGenerateStrategyConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.ComplexShardingStrategyConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.ShardingStrategyConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * mybatis的配置
 */
@Configuration
@EnableTransactionManagement
public class MybatisConf {

    @Bean
    public DataSource initSD() throws SQLException {
        // 配置真实数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        // 配置第一个数据源
        HikariDataSource dataSourceBuilder1 = new HikariDataSource();
        dataSourceBuilder1.setDriverClassName("com.mysql.jdbc.Driver");
        dataSourceBuilder1.setJdbcUrl("jdbc:mysql://localhost:3306/ds0?useSSL=false&userUnicode=true&&characterEncoding=utf8&allowMultiQueries=true");
        dataSourceBuilder1.setUsername("root");
        dataSourceBuilder1.setPassword("123456");
        dataSourceMap.put("ds0", dataSourceBuilder1);

        // 配置第二个数据源
        HikariDataSource dataSourceBuilder2 = new HikariDataSource();
        dataSourceBuilder2.setDriverClassName("com.mysql.jdbc.Driver");
        dataSourceBuilder2.setJdbcUrl("jdbc:mysql://localhost:3306/ds1?useSSL=false&userUnicode=true&&characterEncoding=utf8&allowMultiQueries=true");
        dataSourceBuilder2.setUsername("root");
        dataSourceBuilder2.setPassword("123456");
        dataSourceMap.put("ds1", dataSourceBuilder2);

        /* -------------------------- 配置 order 表 ------------------------- */
        // 配置Order分片表配置  https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/java-api/rules/sharding/#%E5%88%86%E7%89%87%E8%A1%A8%E9%85%8D%E7%BD%AE
        ShardingTableRuleConfiguration orderTableRuleConfig = new ShardingTableRuleConfiguration(
                "t_order", "ds${0..1}.t_order_${0..1}");
        /* -------------------------- 配置 order 分库 + 分表策略 ------------------------- */
        /* --------- 配置分库策略 --------- */
        //分片策略可以分 标准、复合、hint、不分配、范围   https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/java-api/rules/sharding/#分片策略配置
        //shardingColumns：分片列名称
        //shardingAlgorithmName：分片算法名称
        //inline：一种内置的分片算法
        ShardingStrategyConfiguration orderDatabaseConfig = new StandardShardingStrategyConfiguration(
                "user_id", "inline");
        orderTableRuleConfig.setDatabaseShardingStrategy(orderDatabaseConfig);
        /* --------- 配置分表策略 --------- */
        //ShardingStrategyConfiguration orderTableConfig = new StandardShardingStrategyConfiguration(
        //        "order_id", "standard_test_tbl");//自定义分片规则
        ShardingStrategyConfiguration orderTableConfig = new StandardShardingStrategyConfiguration(
                "order_id", "inline-orderId");//内置分片算法
        orderTableRuleConfig.setTableShardingStrategy(orderTableConfig);
        /* --------- 设置主键规则 --------- */
        KeyGenerateStrategyConfiguration keyGenerateStrategyConfiguration = new KeyGenerateStrategyConfiguration("orderId", "snowflake");
        orderTableRuleConfig.setKeyGenerateStrategy(keyGenerateStrategyConfiguration);

        /* -------------------------- 全局sharding规则配置 ------------------------- */
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTables().add(orderTableRuleConfig);//设置分片的库表
        shardingRuleConfig.getTables().add(this.productSharding());//设置分片的库表
        //设置雪花算法
        Properties keyProperties = new Properties();
        keyProperties.setProperty("worker.id", "123");
        keyProperties.setProperty("datacenter.id", "1");
        shardingRuleConfig.getKeyGenerators().put("snowflake", new ShardingSphereAlgorithmConfiguration("SNOWFLAKE", keyProperties));
        //设置分片配置
        Properties props = new Properties();
        props.setProperty("algorithm-expression", "ds$->{user_id % 2}");
        shardingRuleConfig.getShardingAlgorithms().put("inline", new ShardingSphereAlgorithmConfiguration("INLINE", props));
        //设置分片配置
        Properties props2 = new Properties();
        props2.setProperty("algorithm-expression", "product_$->{id % 2}");
        shardingRuleConfig.getShardingAlgorithms().put("inline-city", new ShardingSphereAlgorithmConfiguration("INLINE", props2));
        Properties tableProps = new Properties();
        tableProps.setProperty("algorithm-expression", "t_order_$->{order_id % 3}");
        shardingRuleConfig.getShardingAlgorithms().put("inline-orderId", new ShardingSphereAlgorithmConfiguration("INLINE", props2));
        //自定义的分片规则
        shardingRuleConfig.getShardingAlgorithms().put("standard_test_tbl",
                new ShardingSphereAlgorithmConfiguration(StandardModuloShardingTableAlgorithm.STANDARD_TEST_TBL, new Properties()));
        // 省略配置order_item表规则...
        // ...
        //3、属性配置项，可以为以下属性
        Properties propertie = new Properties();
        //是否打印SQL解析和改写日志
        propertie.setProperty("sql.show", Boolean.TRUE.toString());
//        //用于SQL执行的工作线程数量，为零则表示无限制
//        propertie.setProperty("executor.size","0");
//        //每个物理数据库为每次查询分配的最大连接数量
//        propertie.setProperty("max.connections.size.per.query","1");
//        //是否在启动时检查分表元数据一致性
//        propertie.setProperty("check.table.metadata.enabled","false");
        // 获取数据源对象
        DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(
                dataSourceMap, Collections.singleton(shardingRuleConfig), propertie);
        return dataSource;
    }

    private ShardingTableRuleConfiguration productSharding(){
        ShardingTableRuleConfiguration tableRuleConfig = new ShardingTableRuleConfiguration(
                "product", "ds${0..1}.product_${0..1}");
        /* -------------------------- 配置 order 分库 + 分表策略 ------------------------- */
        /* --------- 配置分库策略 --------- */
        ShardingStrategyConfiguration databaseConfig = new StandardShardingStrategyConfiguration(
                "user_id", "inline");
        tableRuleConfig.setDatabaseShardingStrategy(databaseConfig);
        /* --------- 配置分表策略 --------- */
        ShardingStrategyConfiguration orderTableConfig = new StandardShardingStrategyConfiguration(
                "city_id", "inline-city");//内置分片算法
        tableRuleConfig.setTableShardingStrategy(orderTableConfig);
        /* --------- 设置主键规则 --------- */
        tableRuleConfig.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("ID", "snowflake"));
        //KeyGenerateStrategyConfiguration keyGenerateStrategyConfiguration = new KeyGenerateStrategyConfiguration("id", "snowflake");
        //tableRuleConfig.setKeyGenerateStrategy(keyGenerateStrategyConfiguration);
        return tableRuleConfig;
    }

    /**
     * 获取sqlFactory
     *
     * @return
     * @throws Exception
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        /**
         * sharding-jdbc 产生的DataSource
         */
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(initSD());
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(
                        "mapper/**.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.lanp.springsd.model");
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        try {
            return new DataSourceTransactionManager(initSD());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

}
